package com.ruoyi.taskasses;

import cn.hutool.core.collection.CollectionUtil;
import cn.hutool.core.collection.ListUtil;
import cn.hutool.core.date.DateField;
import cn.hutool.core.date.DateTime;
import cn.hutool.core.date.DateUtil;
import cn.hutool.core.io.FileUtil;
import cn.hutool.core.lang.UUID;
import cn.hutool.core.map.MapBuilder;
import cn.hutool.crypto.digest.MD5;
import cn.hutool.json.JSONArray;
import cn.hutool.json.JSONConfig;
import cn.hutool.json.JSONObject;
import cn.hutool.json.JSONUtil;
import com.baomidou.mybatisplus.core.conditions.query.LambdaQueryWrapper;
import com.ruoyi.common.config.RuoYiConfig;
import com.ruoyi.common.exception.ProcessStopException;
import com.ruoyi.common.exception.ServiceException;
import com.ruoyi.common.utils.ExecUtil;
import com.ruoyi.common.utils.SseEmitterUtils;
import com.ruoyi.common.utils.ZipUtils;
import com.ruoyi.common.utils.biz.BizModule;
import com.ruoyi.common.utils.biz.PathUtil;
import com.ruoyi.common.utils.biz.ResultSaveModule;
import com.ruoyi.dm.component.manager.TrackDataManager;
import com.ruoyi.dm.domain.po.SpaceTargetMeasurePO;
import com.ruoyi.dm.service.ISpaceTargetMeasureService;
import com.ruoyi.system.domain.ResultSave;
import com.ruoyi.system.service.IResultSaveService;
import com.ruoyi.taskasses.domain.AllModeParam;
import com.ruoyi.taskasses.domain.EstimateAnalysisResVO;
import com.ruoyi.taskasses.domain.OtherSpaceResBO;
import com.ruoyi.taskasses.domain.OtherSpaceReturnVO;
import com.ruoyi.trackapply.domain.*;
import lombok.extern.slf4j.Slf4j;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.RedisTemplate;
import org.springframework.stereotype.Service;
import org.springframework.util.StringUtils;

import java.io.BufferedReader;
import java.io.File;
import java.io.FileReader;
import java.io.IOException;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.charset.StandardCharsets;
import java.nio.file.Files;
import java.nio.file.Paths;
import java.util.*;
import java.util.concurrent.TimeUnit;
import java.util.stream.Collectors;
import java.util.stream.Stream;

/**
 * 典型任务评估
 */
@Slf4j
@Service
public class AllModeParamService {

    @Autowired
    private RedisTemplate redisTemplate;

    @Autowired
    private IResultSaveService resultSaveService;

    @Autowired
    private TrackDataManager trackDataManager;

    @Autowired
    private ISpaceTargetMeasureService spaceTargetMeasureService;

    @Autowired
    private OtherSpaceService otherSpaceService;

    public File download(AllModeParam param) throws IOException {
        String method = param.getMethod();
        if (!StringUtils.hasLength(method)) {
            //兼容 ,默认是 定轨
            method="dg";
        }

        if ("dg".equals(method)) {
            return this.dgDownload(param);
        }else if ("yb".equals(method)) {
            //参数转化
            return otherSpaceService.download(param);
        }else {
            return null;
        }
    }

    private File dgDownload(AllModeParam param) throws IOException {
        List<String> dataList = findResult(param);
        String timeStr = DateUtil.format(new Date(), "yyyyMMddHHmmss");
        String zipPath = RuoYiConfig.getDownloadPath() + "/典型任务模型评估-"+timeStr+".zip";
        ZipUtils.zipWithPath(dataList,zipPath);
        return new File(zipPath);
    }

    public void save(AllModeParam param) {
        String method = param.getMethod();

        String model = ResultSaveModule.TASK_ASSESS;
        String subModel = ResultSaveModule.TASK_ASSESS_ON;
        if ("dg".equals(method)) {
            dgSave(param,model,subModel);
        }else if ("yb".equals(method)) {
            //参数转化
            otherSpaceService.ybSave(param,model,subModel);
        }else {
        }

    }

    public void dgSave(AllModeParam param,String model,String subModel) {
        List<String> dataList = findResult(param);

        param.setSseId(null);
        ResultSave entity = ResultSave.builder()
                .params("")
                .module(model)
                .submodule(subModel)
                .resultData(JSONUtil.toJsonStr(dataList))
                .startTime(DateUtil.format(param.getStartTime(),"yyyy-MM-dd"))
                .endTime(DateUtil.format(param.getEndTime(),"yyyy-MM-dd"))
                .createTime(new Date())
                .build();
        resultSaveService.resultSave(param,entity);
    }

    private List<String> findResult(AllModeParam param) {
        String resList =(String) redisTemplate.opsForValue().get(MD5.create().digestHex(param.toString()));
        if (!StringUtils.hasLength(resList)) {
            throw new ServiceException("请先计算后再保存");
        }
        JSONArray array = JSONUtil.parseArray(resList);
        List<String> dataList = new ArrayList<>();
        for (Object o : array) {
            String resStr = (String)o;
            String[] resStrs = resStr.split("[\n\r]+");
            String firsLine = resStrs[0];
            String thirdLine = resStrs[2];
            //评估结果
            //String ninthLine = resStrs[9];
            String jsonStr = JSONUtil.toJsonStr(ListUtil.of(firsLine, thirdLine));
            //String jsonStr = JSONUtil.toJsonStr(ListUtil.of(firsLine, thirdLine, ninthLine));
            String path = PathUtil.outputModuleDir(BizModule.TASK_ASSESS)+"/allmode/评估-"+UUID.fastUUID().toString()+ ".json";
            FileUtil.writeString(jsonStr,path,"utf-8");
            dataList.add(path);
            String fifthLine = resStrs[5];
            dataList.add(fifthLine);
        }
        return dataList;
    }
    

    public OtherSpaceReturnVO cal(AllModeParam param) throws Exception {
        String method = param.getMethod();
        if (!StringUtils.hasLength(method)) {
            //兼容 ,默认是 定轨
            method="dg";
        }

        if ("dg".equals(method)) {
            return this.dgMethod(param);
        }else if ("yb".equals(method)) {
            //参数转化
            return otherSpaceService.ybMethodcal(param);
        }else {
            return null;
        }
    }

    /**
     * 定轨方法
     * @param param
     * @return
     * @throws Exception
     */
    public OtherSpaceReturnVO dgMethod(AllModeParam param) throws Exception {
        //进度指示
        int percent = 1;
        SseEmitterUtils.sendMessage(param.getSseId(),++percent+"0");
        //观测时间
        DateTime obsStart = DateTime.of(param.getStartTime());
        DateTime obsEnd = DateTime.of(param.getEndTime());

        //历元时刻
        DateTime calenderStart = DateUtil.offset(obsStart, DateField.DAY_OF_MONTH, 1);
        DateTime calenderEnd = DateUtil.offset(obsEnd, DateField.DAY_OF_MONTH, 1);
        DgInputJsonBO dgInputJsonBO = new DgInputJsonBO();
        dgInputJsonBO.setAlgoDirPath("/dgdq/code/dgdq-algorithm/taskAssess/To16OD0708/data/starNetOD/");
        dgInputJsonBO.setStartEpoch(calenderStart);
        dgInputJsonBO.setEndEpoch(calenderEnd);
        dgInputJsonBO.setObsStartEpoch(obsStart);
        dgInputJsonBO.setObsEndEpoch(obsEnd);

        //1 依据 卫星名称,空间目标测量数据表  查询卫星属性 并写入 /code/dgdq-algorithm/taskAssess/To16OD0708/data/starNetOD/satConst.json
        LambdaQueryWrapper<SpaceTargetMeasurePO> measureQw = new LambdaQueryWrapper<>();
        measureQw.select(SpaceTargetMeasurePO::getCode,SpaceTargetMeasurePO::getName,SpaceTargetMeasurePO::getDens,SpaceTargetMeasurePO::getWindArea,SpaceTargetMeasurePO::getFIndex,SpaceTargetMeasurePO::getLightIndex);
        measureQw.eq(SpaceTargetMeasurePO::getName,param.getSateName());
        SpaceTargetMeasurePO measure = spaceTargetMeasureService.getOne(measureQw);
        if (null == measure) {
            throw new ServiceException("未找到 "+param.getSateName()+" 的目标测量数据,请先去目标测量页面增加数据!");
        }
        ForceModelBO forceModelBO = new ForceModelBO();

        if (StringUtils.hasLength(param.getWindModel())) {
            switch (param.getWindModel()) {
                case "HWM07":
                    forceModelBO.setWindFieldModel(1);
                    break;
                case "HWCM":
                    forceModelBO.setWindFieldModel(2);
                    break;
                default:
                    forceModelBO.setWindFieldModel(0);
            }
        }
        forceModelBO.setArea(Double.valueOf(measure.getWindArea()));
        forceModelBO.setCr(Double.valueOf(measure.getLightIndex()));
        forceModelBO.setCd(Double.valueOf(measure.getFIndex()));

        //GNSSFileDirPath路径 要去数据库筛选 观测 起止 时间内的 并写入
        //List<CollectGnssPO> gnnsPos = trackDataManager.queryGnnsInMinute(measure.getName().toUpperCase(), obsStart, obsEnd);
        List<CollectGnssPO> gnnsPos = trackDataManager.queryGnns(param.getType(),measure.getName().toUpperCase(), obsStart, obsEnd);
        log.info("找到 {}-{}  GNNS数据:{}", obsStart, obsEnd, null != gnnsPos ? gnnsPos.size() : 0);

        SseEmitterUtils.sendMessage(param.getSseId(),++percent+"0");

        File gnnsFile = new File(PathUtil.tmpModuleDir(BizModule.TASK_ASSESS), "gnns/"+UUID.fastUUID().toString(true)+"/GNSS.txt");
        gnnsFile.getParentFile().mkdirs();

        FileUtil.writeLines(gnnsPos, gnnsFile, "utf-8");
        SatConstBO satConstBO = new SatConstBO();
        satConstBO.setGNSSFileDirPath(gnnsFile.getParent());
        satConstBO.setForceModel(forceModelBO);


        JSONConfig jsonConfig = JSONConfig.create().setIgnoreCase(false);
        jsonConfig.setDateFormat("yyyy-MM-dd HH:mm:ss");


        //从数据库获取 查2个时间间隔  单位秒
        int step = trackDataManager.queryStep(gnnsPos);
        dgInputJsonBO.setStep(step);
        dgInputJsonBO.setSatId(Integer.valueOf(measure.getCode()));
        dgInputJsonBO.setSatName(measure.getName());
        dgInputJsonBO.setSatMass(Double.valueOf(measure.getDens()));


        List<String> models = param.getModel();
        List<OtherSpaceReturnVO> reses = new ArrayList<>();
        List<String> resList = new ArrayList<>();
        for (String model : models) {
            forceModelBO.setDensityModel(checkModel(model));
            String satJson = JSONUtil.toJsonStr(MapBuilder.create().put("Input", satConstBO).build(), jsonConfig);
            log.info("定轨 satJson 命令:{}", satJson);
            String satJsonStr = "/dgdq/code/dgdq-algorithm/taskAssess/To16OD0708/data/starNetOD/satConst.json";
            FileUtil.writeString(satJson, satJsonStr, StandardCharsets.UTF_8);

            String inputJson = JSONUtil.toJsonStr(MapBuilder.create().put("Input", dgInputJsonBO).build(), jsonConfig);
            log.info("定轨 inputJson 命令:{}", inputJson);
            String inputJsonPath = PathUtil.tmpModuleDir(BizModule.TASK_ASSESS) + "/" + UUID.fastUUID().toString() + ".json";
            FileUtil.writeString(inputJson, inputJsonPath, StandardCharsets.UTF_8);
            //3调用算法
            String res = exec(inputJsonPath,param.getSunMin(),param.getSunMax(),param.getGroundMin(),param.getGroundMax(),param.getSseId());
            resList.add(res);
            String[] resStrs = res.split("[\n\r]+");
            log.info("执行命令结果:{}", JSONUtil.toJsonStr(resStrs));
            List<String> list = CollectionUtil.newArrayList(resStrs);
            OtherSpaceReturnVO innerVO = dealRes(list, obsStart,model);
            reses.add(innerVO);
            Thread.sleep(1000);
            SseEmitterUtils.sendMessage(param.getSseId(),++percent+"0");
        }
        redisTemplate.opsForValue().set(MD5.create().digestHex(param.toString()),JSONUtil.toJsonStr(resList),1, TimeUnit.HOURS);


        OtherSpaceReturnVO returnVO = new OtherSpaceReturnVO();
        OtherSpaceReturnVO.TimeResBO timeResBO = returnVO.new TimeResBO();
        returnVO.setModels(models);
        returnVO.setSolarIndex(reses.get(0).getSolarIndex());
        returnVO.setGeoIndex(reses.get(0).getGeoIndex());
        List<EstimateAnalysisResVO> estimates = new ArrayList<>();
        Map<String, List> innerXmap = new TreeMap<>();
        Map<String, List> innerYmap = new TreeMap<>();
        Map<String, List> innerZmap = new TreeMap<>();
        for (OtherSpaceReturnVO res : reses) {
            estimates.addAll(res.getEstimate());
            Map<String, List> innerMapX = res.getTimeRes().getXres();
            for (Map.Entry<String, List> mm : innerMapX.entrySet()) {
                innerXmap.put(mm.getKey(),mm.getValue());
            }
            Map<String, List> innerMapY = res.getTimeRes().getYres();
            for (Map.Entry<String, List> mm : innerMapY.entrySet()) {
                innerYmap.put(mm.getKey(),mm.getValue());
            }
            Map<String, List> innerMapZ = res.getTimeRes().getZres();
            for (Map.Entry<String, List> mm : innerMapZ.entrySet()) {
                innerZmap.put(mm.getKey(),mm.getValue());
            }
        }
        timeResBO.setXres(innerXmap);
        timeResBO.setYres(innerYmap);
        timeResBO.setZres(innerZmap);
        timeResBO.setTime(reses.get(0).getTimeRes().getTime());
        returnVO.setEstimate(estimates);
        returnVO.setTimeRes(timeResBO);

        //判断哪个是最优模型
        EstimateAnalysisResVO bestVO = estimates.stream().min(Comparator.comparing(EstimateAnalysisResVO::getRmse)).get();
        returnVO.setResult(String.format("所选时间范围内，最优模型为：%s",bestVO.getModel()));

        //临时文件删除
        FileUtil.del(gnnsFile.getParentFile());
        SseEmitterUtils.sendMessage(param.getSseId(),++percent+"0");
        return returnVO;
    }

    private int checkModel(String model) {
        //1.HADCAM(大气动态修正模型)；2.HALAM（高精度构建模型）；3.NRLMSISE-00 ；4.Jacchia71；5.JB2008 ；6.DTM2020。
        if ("HADCAM".equals(model)) {
            return 1;
        } else if ("HALAM".equals(model)) {
            return 2;
        } else if ("NRLMSISE-00".equals(model)) {
            return 3;
        } else if ("Jacchia71".equals(model)) {
            return 4;
        } else if ("JB2008".equals(model)) {
            return 5;
        }else if ("DTM2020".equals(model)) {
            return 6;
        }else {
            throw new ServiceException("无效的模型名称:"+model);
        }
    }

    /**
     * 处理算法返回
     *
     * @param resStrs
     * @param obsStart
     * @param model
     * @return
     */
    private OtherSpaceReturnVO dealRes(List<String> resStrs, DateTime obsStart, String model) throws Exception {
        OtherSpaceReturnVO otherSpaceReturnVO = new OtherSpaceReturnVO();
        OtherSpaceReturnVO.IndexBO ap = otherSpaceReturnVO.new IndexBO();
        OtherSpaceReturnVO.IndexBO f107 = otherSpaceReturnVO.new IndexBO();
        OtherSpaceReturnVO.TimeResBO timeResBO = otherSpaceReturnVO.new TimeResBO();
        //太阳地磁极值
        String firsLine = resStrs.get(0);
        JSONObject apF107 = JSONUtil.parseObj(firsLine);
        ap.setMax(apF107.getInt("geoma_index_max"));
        ap.setMin(apF107.getInt("geoma_index_min"));
        f107.setMin(apF107.getInt("sloar_index_min"));
        f107.setMax(apF107.getInt("solar_index_max"));
        String thirdLine = resStrs.get(1);
        JSONObject apF107Data = JSONUtil.parseObj(thirdLine);
        ap.setIndexDate(apF107Data.getJSONArray("AP_datetime_list"));
        ap.setIndex(apF107Data.getJSONArray("AP_index_data_list"));
        f107.setIndexDate(apF107Data.getJSONArray("F107_datetime_list"));
        f107.setIndex(apF107Data.getJSONArray("F107_index_data_list"));

        //评估结果
        String ninthLine = resStrs.get(8);
        JSONArray ninthArr = JSONUtil.parseArray(ninthLine);
        JSONObject ninthObj = ninthArr.getJSONObject(0);
        JSONArray errorList = ninthObj.getJSONArray("errorList");
        JSONObject estimateObj = errorList.getJSONObject(0);
        OtherSpaceResBO.ErrorBo value = estimateObj.toBean(OtherSpaceResBO.ErrorBo.class);
        EstimateAnalysisResVO x = EstimateAnalysisResVO.builder().model(model).avg(new BigDecimal(value.getX_average()))
                .rmse(new BigDecimal(value.getX_RMS())).
                max(new BigDecimal(value.getX_max())).
                min(new BigDecimal(value.getX_min())).dire("X").build();
        EstimateAnalysisResVO y = EstimateAnalysisResVO.builder().model(model)
                .avg(new BigDecimal(value.getY_average()))
                .rmse(new BigDecimal(value.getY_RMS()))
                .max(new BigDecimal(value.getY_max()))
                .min(new BigDecimal(value.getY_min())).dire("Y").build();
        EstimateAnalysisResVO z = EstimateAnalysisResVO.builder().model(model)
                .avg(new BigDecimal(value.getZ_average()))
                .rmse(new BigDecimal(value.getZ_RMS()))
                .max(new BigDecimal(value.getZ_max()))
                .min(new BigDecimal(value.getZ_min())).dire("Z").build();
        EstimateAnalysisResVO xyz = EstimateAnalysisResVO.builder().model(model)
                .avg(new BigDecimal(value.getXYZ_average()))
                .rmse(new BigDecimal(value.getXYZ_RMS()))
                .max(new BigDecimal(value.getXYZ_max()))
                .min(new BigDecimal(value.getXYZ_min())).dire("XYZ").build();


        //时许分布结果  只需要 GNSS_O-C.TXT 的  xyz
        String fifthLine = resStrs.get(4);
        Object[] objects = makeupOcVO(fifthLine, obsStart, model);
        OcVO oc = (OcVO)objects[0];
        List<String> object = (List) objects[1];
        List<Date> timeList= object.stream().map(str ->{
            DateTime parse = DateUtil.parse(str);
            return parse;
            }).collect(Collectors.toList());
        timeResBO.setTime(timeList);
        HashMap<String, List> xdataMap = new HashMap<>();
        HashMap<String, List> ydataMap = new HashMap<>();
        HashMap<String, List> zdataMap = new HashMap<>();
        xdataMap.put(model+"-X",oc.getX());
        ydataMap.put(model+"-Y",oc.getY());
        zdataMap.put(model+"-Z",oc.getZ());
        timeResBO.setXres(xdataMap);
        timeResBO.setYres(ydataMap);
        timeResBO.setZres(zdataMap);


        otherSpaceReturnVO.setSolarIndex(f107);
        otherSpaceReturnVO.setGeoIndex(ap);
        otherSpaceReturnVO.setEstimate(ListUtil.of(x,y,z,xyz));
        otherSpaceReturnVO.setTimeRes(timeResBO);

        return otherSpaceReturnVO;
    }


    private String exec(String jsonPath,int sunMin,int sunMax,int apMin,int apMax,String sseid) throws ProcessStopException {

        //python /all_eval_mode.py 90 150 15 40 /dgdq/code/dgdq-algorithm/taskAssess/To16OD0708/data/starNetOD/Input.json
        String script = PathUtil.codeModuleDir(BizModule.TASK_ASSESS) + PathUtil.moduleDirs().getTaskAssess().get("allMode");

        String commond = String.format("cd /dgdq/code/dgdq-algorithm/taskAssess/To16OD0708/bin && python %s %d %d %d %d  %s", script,sunMin,sunMax,apMin,apMax, jsonPath);
        List<String> resLines = ExecUtil.execPythonZw(Stream.of("bash", "-c", commond).collect(Collectors.toList()), sseid);
        String res = CollectionUtil.join(resLines,"\n");
        return res;

    }

    /**
     * 组装OcVO返回值
     * 因为时间都一样，故只处理一次， 返回  [oc, 时间数组, ]
     *
     * @param filePath 文件路径
     * @param model
     * @return
     * @throws Exception
     */
    private Object[] makeupOcVO(String filePath, Date obsTime, String model) throws Exception {
        //获取行数
        File file = new File(filePath);
        long lines = Files.lines(Paths.get(file.getPath())).count();
        //页面展示1000条数据
        int num = 1000;
        int skip = 1;
        if (lines > 1000) {
            skip = (int) (lines / num);
        }
        List<String> timeList = new ArrayList<>(110);
        List<BigDecimal> xList = new ArrayList<>(110);
        List<BigDecimal> yList = new ArrayList<>(110);
        List<BigDecimal> zList = new ArrayList<>(110);
        BufferedReader reader = new BufferedReader(new FileReader(file));
        int lineNum = 0;
        String line = "";
        while (null != (line = reader.readLine())) {
            //60S  ObsStartEpoch为起点
            //      0.0000        -0.0039        -0.0032        -0.0022     0.00000028    -0.00000006     0.00000047
            if (lineNum % skip == 0) {
                String[] split = line.split("\\s+");
                DateTime offset = DateUtil.offset(obsTime, DateField.SECOND, (Double.valueOf(split[1]).intValue()));
                String format = DateUtil.format(offset, "yyyy-MM-dd HH:mm:ss");
                timeList.add(format);
                xList.add(new BigDecimal(split[2]).setScale(8,RoundingMode.HALF_UP));
                yList.add(new BigDecimal(split[3]).setScale(8,RoundingMode.HALF_UP));
                zList.add(new BigDecimal(split[4]).setScale(8,RoundingMode.HALF_UP));
            }
            lineNum++;
        }

        OcVO oc = new OcVO();
        oc.setModel(model);
        oc.setX(xList);
        oc.setY(yList);
        oc.setZ(zList);
        return new Object[]{oc, timeList};
    }
}
